using UnityEngine;
using System.Collections;

public class Entity : MonoBehaviour {
	private LockPoint localLockPoint = null;
	private LockPoint targetLockPoint = null;
	
	void Update() {
		if (!isLocked()) {
			updateLock();
		}
	}
	
	// Called by the parent to update the lock down through
	// the hierarchy.
	public void updateLock() {
		if (isLocked()) {
			GameObject a = localLockPoint.gameObject;
			GameObject b = targetLockPoint.gameObject;
			
			Transform aParent = transform;
			Transform bParent = b.transform.parent;
			
			if (aParent != null 
			&&  bParent != null) {
				Quaternion tempRot = Quaternion.LookRotation(-b.transform.forward, a.transform.up);
				aParent.transform.rotation = tempRot * Quaternion.Inverse(a.transform.localRotation);
				aParent.transform.position = aParent.transform.position + (b.transform.position - a.transform.position);
			} else {
				Debug.Log("Failed to fetch snap parents. (aParent:'" + aParent + "' or bParent:'" + bParent + "')");
			}
		}
		
		for (int i = 0; i < transform.childCount; i++) {
			Entity child = transform.GetChild(i).GetComponent<Entity>();
			
			if (child != null) {
				child.updateLock();
			}
		}
	}
	
	// The function should return true if the recursion should
	// terminate.
	public bool each(System.Func<GameObject, bool> f) {
		if (f(gameObject)) return true;
		
		for (int i = 0; i < transform.childCount; i++) {
			GameObject child = transform.GetChild(i).gameObject;
			
			if (child.GetComponent<Entity>() != null) {
				if (child.GetComponent<Entity>().each(f)) return true;
			} else {
				if (f(child)) return true;
			}
		}
		
		return false;
	}
	
	// A pair of two LockPoints.
	public class Pair {
		public LockPoint local = null; 
		public LockPoint target = null;
		
		public bool isSet() {
			return local != null && target != null;	
		}
	};
	
	// Returns the pair of lock points that will be used if the 
	// lockTowards-function is called now.
	public Pair findLocks(Entity other) {
		// Find nearest pair.
		Pair pair = new Pair();
		
		if (other != null) {
			eachLockPoint(delegate(LockPoint point) {
				LockPoint test = findNearestTo(other, point);
				
				if (pair.local == null
				||  pair.target == null
				||  distance (point, test) < distance (pair.local, pair.target)) {
					pair.local  = point;
					pair.target = test;
				}
			});
		}
		
		return pair;
	}
	
	public void setMaterial(Material mat) {
		each(delegate(GameObject obj) {
			if (obj.GetComponent<Renderer>() != null) {
				obj.renderer.material = mat;
			}
			
			return false;
		});
	}

	public void releaseLock() {
		localLockPoint  = null;
		targetLockPoint = null;
		transform.parent = null;
	}
	
	// Attaches this entity to some other Entity in the specified
	// hierarchy.
	public void lockTowards(Entity other) {
		// Find nearest pair.
		Pair pair = findLocks(other);
		
		localLockPoint  = pair.local;
		targetLockPoint = pair.target;
		
		// Attach in hierarchy.
		transform.parent = pair.target.transform.parent;
	}
	
	// Returns true if this Entity is locked to another.
	public bool isLocked() {
		return !(localLockPoint == null || targetLockPoint == null);
	}
	
	private LockPoint findNearestTo(Entity other, LockPoint local) {
		LockPoint nearest = null;
		
		other.eachLockPoint(delegate(LockPoint point) {
			if (nearest == null 
			||  distance (point, local) < distance (nearest, local)) {
				nearest = point;
			}
		});
		
		return nearest;
	}
	
	private float distance(LockPoint a, LockPoint b) {
		return (a.transform.position - b.transform.position).magnitude;
	}
	
	// Performes the specified action on all SnapPoints in this 
	// Entity, but not in entities below this one.
	private void eachLockPoint(System.Action<LockPoint> callback) {
		for (int i = 0; i < transform.childCount; i++) {
			Transform child = transform.GetChild(i);
			
			if (child.GetComponent<LockPoint>() != null) {
				callback(child.GetComponent<LockPoint>());
			} else if (child.GetComponent<Entity>() != null) {
				child.GetComponent<Entity>().eachLockPoint(callback);
			}
		}
	}
}
